package com.hins.component.hlock.core;

import com.hins.component.hlock.annotation.Hlock;
import com.hins.component.hlock.handler.HlockInvocationException;
import com.hins.component.hlock.lock.Lock;
import com.hins.component.hlock.lock.LockFactory;
import lombok.Data;
import lombok.Setter;
import com.hins.component.hlock.model.LockInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 给添加@HLock切面加锁处理
 *
 * @author Leslie
 * @date 2021/9/7 9:49
 */
@Aspect
@Component
@Order(0)
public class HlockAspectHandler {

    private static final Logger logger = LoggerFactory.getLogger(HlockAspectHandler.class);

    @Setter(onMethod = @__(@Autowired))
    private LockFactory lockFactory;

    @Setter(onMethod = @__(@Autowired))
    private LockInfoProvider lockInfoProvider;

    private final Map<String, LockRes> currentThreadLock = new ConcurrentHashMap<>();

    @Around(value = "@annotation(hlock)")
    public Object around(ProceedingJoinPoint joinPoint, Hlock hlock) throws Throwable {
        LockInfo lockInfo = lockInfoProvider.get(joinPoint, hlock);
        String currentLock = this.getCurrentLockId(joinPoint, hlock);
        currentThreadLock.put(currentLock, new LockRes(lockInfo, false));

        Lock lock = lockFactory.getLock(lockInfo);

        // 等待时间超过还没有获取就获取失败
        boolean lockRes = lock.acquire();

        if (!lockRes) {
            if (logger.isWarnEnabled()) {
                logger.warn("Timeout while acquiring Lock({})", lockInfo.getName());
            }
            //如果自定义了获取锁失败的处理策略，则执行自定义的降级处理策略
            if (!StringUtils.isEmpty(hlock.customLockTimeoutStrategy())) {

                return handleCustomLockTimeout(hlock.customLockTimeoutStrategy(), joinPoint);

            } else {
                //否则执行预定义的执行策略
                //注意：如果没有指定预定义的策略，默认的策略为静默啥不做处理
                hlock.lockTimeoutStrategy().handle(lockInfo, lock, joinPoint);
            }
        }

        currentThreadLock.get(currentLock).setLock(lock);
        // 之后用来释放锁的
        currentThreadLock.get(currentLock).setRes(true);

        return joinPoint.proceed();
    }

    @AfterReturning(value = "@annotation(hlock)")
    public void afterReturning(JoinPoint joinPoint, Hlock hlock) throws Throwable {
        String currentLock = this.getCurrentLockId(joinPoint, hlock);
        // 这里注意要是业务线程还没执行完就到了释放的时间的话，其它线程是可以进来的，然后执行业务流程，然后第一个线程就会报释放超时执行，设置了超时时间的话就会这样，不设置超时时间就搞一个看门狗
        releaseLock(hlock, joinPoint, currentLock);
        cleanUpThreadLocal(currentLock);
    }

    @AfterThrowing(value = "@annotation(hlock)", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Hlock hlock, Throwable ex) throws Throwable {
        String currentLock = this.getCurrentLockId(joinPoint, hlock);
        releaseLock(hlock, joinPoint, currentLock);
        cleanUpThreadLocal(currentLock);
        throw ex;
    }

    /**
     * 获取当前锁在map中的key
     */
    private String getCurrentLockId(JoinPoint joinPoint, Hlock hlock) {
        LockInfo lockInfo = lockInfoProvider.get(joinPoint, hlock);
        return Thread.currentThread().getId() + lockInfo.getName();
    }

    /**
     * 释放锁
     */
    private void releaseLock(Hlock hlock, JoinPoint joinPoint, String currentLock) throws Throwable {
        LockRes lockRes = currentThreadLock.get(currentLock);
        if (Objects.isNull(lockRes)) {
            throw new NullPointerException("Please check whether the input parameter used as the com.hins.component.hlock.lock key value has been modified in the method, which will cause the acquire and release locks to have different key values and throw null pointers.currentLockKey:" + currentLock);
        }
        if (lockRes.getRes()) {
            boolean releaseRes = currentThreadLock.get(currentLock).getLock().release();
            // avoid release com.hins.component.hlock.lock twice when exception happens below
            lockRes.setRes(false);
            if (!releaseRes) {
                handleReleaseTimeout(hlock, lockRes.getLockInfo(), joinPoint);
            }
        }
    }

    private void cleanUpThreadLocal(String currentLock) {
        currentThreadLock.remove(currentLock);
    }

    /**
     * 处理自定义加锁超时
     */
    private Object handleCustomLockTimeout(String lockTimeoutHandler, JoinPoint joinPoint) throws Throwable {

        // prepare invocation context
        Method currentMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object target = joinPoint.getTarget();
        Method handleMethod;
        try {
            handleMethod = joinPoint.getTarget().getClass().getDeclaredMethod(lockTimeoutHandler, currentMethod.getParameterTypes());
            handleMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Illegal com.hins.component.hlock.annotation param customLockTimeoutStrategy", e);
        }
        Object[] args = joinPoint.getArgs();

        // invoke
        Object res;
        try {
            res = handleMethod.invoke(target, args);
        } catch (IllegalAccessException e) {
            throw new HlockInvocationException("Fail to invoke custom com.hins.component.hlock.lock timeout com.hins.component.hlock.handler: " + lockTimeoutHandler, e);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }

        return res;
    }

    /**
     * 处理释放锁时已超时
     */
    private void handleReleaseTimeout(Hlock hlock, LockInfo lockInfo, JoinPoint joinPoint) throws Throwable {

        if (logger.isWarnEnabled()) {
            logger.warn("Timeout while release Lock({})", lockInfo.getName());
        }

        if (!StringUtils.isEmpty(hlock.customReleaseTimeoutStrategy())) {

            handleCustomReleaseTimeout(hlock.customReleaseTimeoutStrategy(), joinPoint);

        } else {
            hlock.releaseTimeoutStrategy().handle(lockInfo);
        }

    }

    /**
     * 处理自定义释放锁时已超时
     */
    private void handleCustomReleaseTimeout(String releaseTimeoutHandler, JoinPoint joinPoint) throws Throwable {
        Method currentMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object target = joinPoint.getTarget();
        Method handleMethod;

        try {
            // 这里要放在同一个目标类下面才可以反射执行到
            handleMethod = joinPoint.getTarget().getClass().getDeclaredMethod(releaseTimeoutHandler, currentMethod.getParameterTypes());
            handleMethod.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Illegal com.hins.component.hlock.annotation param customReleaseTimeoutStrategy", e);
        }
        Object[] args = joinPoint.getArgs();

        try {
            handleMethod.invoke(target, args);
        } catch (IllegalAccessException e) {
            throw new HlockInvocationException("Fail to invoke custom release timeout com.hins.component.hlock.handler: " + releaseTimeoutHandler, e);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }

    @Data
    private static class LockRes {

        private LockInfo lockInfo;
        private Lock lock;
        private Boolean res;

        LockRes(LockInfo lockInfo, Boolean res) {
            this.lockInfo = lockInfo;
            this.res = res;
        }
    }
}
